﻿//===========================================================================================================================
// Cuberry - http://www.cuberry.net
// Copyright (c) 2012
// by Peacequare® - Amir Moussa
//===========================================================================================================================
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 
// as published by the Free Software Foundation, either version 3 of the License, or any later version.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation 
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and 
// to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions 
// of the Software.
//
// This program is distributed in the hope that it will be useful.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 
// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
// DEALINGS IN THE SOFTWARE.
//
// See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//===========================================================================================================================

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;


namespace P2.Cuberry.Framework.Helper
{
    public interface IShowableModule
    {
        void Show();
        void Hide();
        string ModuleName { get; }
    }


    public class ModuleRepository
    {
        public static Dictionary<string, IShowableModule> Modules = new Dictionary<string, IShowableModule>();
        public static string LoadedModule = null;

        public static string ModulesLocation
        {
            get
            {
                string location = P2.Cuberry.Framework.Helper.Configuration.ConfigurationSetUp.GetAppConfigValue("ModulesLocation");
                if (string.IsNullOrEmpty(location))
                    location = @".";

                return location;
            }
        }


        public static string ModulesRepositoryPath
        {
            get
            {
                string location = P2.Cuberry.Framework.Helper.Configuration.ConfigurationSetUp.GetAppConfigValue("ModulesRepositoryPath");
                if (string.IsNullOrEmpty(location))
                    location = @".";

                return location;
            }
        }


        public static string ModulesRepositoryType
        {
            get
            {
                string location = P2.Cuberry.Framework.Helper.Configuration.ConfigurationSetUp.GetAppConfigValue("ModulesRepositoryType");
                if (string.IsNullOrEmpty(location))
                    location = @"";

                return location.ToLower();
            }
        }


        private static void SaveModuleToDb(string moduleName, string version, string location)
        {
            string[] locationParts = location.Split('\\');
            string fileName = locationParts.Last();
            if (File.Exists(fileName))
            {
                MemoryStream ms = new MemoryStream();
                BinaryReader binReader =
                    new BinaryReader(File.Open(location, FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
                try
                {

                    int bufferSize = 1024 * 8;
                    byte[] readbytes;
                    do
                    {
                        readbytes = binReader.ReadBytes(bufferSize);
                        // save to db
                        ms.Write(readbytes, 0, readbytes.Length);
                    } while (readbytes.Length > 0);

                    string connectionString = P2.Cuberry.Framework.Helper.Configuration.ConfigurationSetUp.GetEntConnectionString("Business", Properties.Settings.Default.EntityResourcesSecurity);
                    using (DAL.SecurityDataClassesDataContext db = new DAL.SecurityDataClassesDataContext(connectionString))
                    {
                        DAL.XConfigModules editedItem;
                        try
                        {
                            editedItem = (from m in db.XConfigModules
                                          where m.ModuleName == moduleName
                                          select m).First();
                            editedItem.EditedOn = DateTime.Now;
                            editedItem.EditedBy = Security.User.Current.UserID;
                            editedItem.Version = version;
                            editedItem.AssemblyContent = ms.ToArray();

                        }
                        catch (InvalidOperationException) //AM:TC
                        {
                            // empty collection
                            editedItem = new DAL.XConfigModules()
                            {
                                ModuleName = moduleName,
                                CreatedBy = Security.User.Current.UserID,
                                CreatedOn = DateTime.Now,
                                EditedBy = Security.User.Current.UserID,
                                EditedOn = DateTime.Now,
                                Version = version,
                                FileName = fileName,
                                AssemblyContent = ms.ToArray()
                            };
                            db.AddToXConfigModules(editedItem);

                        }
                        db.SaveChanges();
                    }
                }
                // If the end of the stream is reached before reading
                // the four data values, ignore the error and use the
                // default settings for the remaining values.
                catch (EndOfStreamException e)
                {
                    Console.WriteLine("{0} caught and ignored. " +
                        "Using default values.", e.GetType().Name);
                }
                finally
                {
                    binReader.Close();
                }
            }
        }


        private static void LoadModuleFromDb(string moduleName)
        {
            try
            {
                string location = ModulesLocation;
                if (!location.EndsWith("\\"))
                {
                    location += "\\";
                }
                MemoryStream ms = new MemoryStream();
                string connectionString = P2.Cuberry.Framework.Helper.Configuration.ConfigurationSetUp.GetEntConnectionString("Business", Properties.Settings.Default.EntityResourcesSecurity);
                using (DAL.SecurityDataClassesDataContext db = new DAL.SecurityDataClassesDataContext(connectionString))
                {
                    DAL.XConfigModules editedItem;

                    editedItem = (from m in db.XConfigModules
                                  where m.ModuleName == moduleName
                                  select m).First();

                    BinaryWriter binWriter =
                new BinaryWriter(File.Open(location + editedItem.FileName, FileMode.Create, FileAccess.Write, FileShare.None));
                    binWriter.Write(editedItem.AssemblyContent);
                    P2Log.LogInfo("LoadModuleFromDb", string.Format("Module {0}, {1}, {2} loaded from DB", editedItem.ModuleName, editedItem.Version, editedItem.FileName));
                }
            }
            catch (InvalidOperationException ex) //AM:TC
            {
                // empty collection
                throw ex;
            }
            catch (Exception ex) //AM:TC
            {
                // empty collection
                //P2Log.LogError(Environment.UserName.ToString(), "LoadModuleFromDb", String.Format("Error loading module {0} from DB ", moduleName), ex);
                P2ExceptionHandler.LogException(ex, string.Format("{0}.{1}", "P2ModuleRepository", System.Reflection.MethodInfo.GetCurrentMethod().Name));
            }

        }


        private static void LoadModuleFromFileSystem(string fileName)
        {
            try
            { 
                string location = ModulesLocation; 
                string sourceLocation = ModulesRepositoryPath;
                if (!sourceLocation.EndsWith("\\"))
                {
                    sourceLocation += "\\";
                }
                if (!location.EndsWith("\\"))
                {
                    location += "\\";
                }
                location += fileName;
                sourceLocation += fileName;
           
                FileVersionInfo viSource = FileVersionInfo.GetVersionInfo(sourceLocation);
                System.IO.File.Copy(sourceLocation, location, true); 

                P2Log.LogInfo("LoadModuleFromFileSystem", string.Format("Module {0}, {1} loaded from file system", sourceLocation, viSource.FileVersion));
            }
            catch (InvalidOperationException exx)
            {
                // empty collection
                P2ExceptionHandler.LogException(exx, string.Format("{0}.{1}", "P2ModuleRepository", System.Reflection.MethodInfo.GetCurrentMethod().Name));
            }
            catch (Exception ex) //AM:TC
            {
                //P2Log.LogError(Security.User.Current.UserID, string.Format("LoadModulesFromDb({0})", location), ex);
                P2ExceptionHandler.LogException(ex, string.Format("{0}.{1}", "P2ModuleRepository", System.Reflection.MethodInfo.GetCurrentMethod().Name));
            }
        }


        private static void LoadModulesFromDb(string location)
        {
            try
            {
                // check filesystem
                DirectoryInfo directory = new DirectoryInfo(location);
                Assembly[] alreadyLoadedAssemblies = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies();
                var moduleFiles = directory.GetFiles("*.dll")
                    .Where(file => alreadyLoadedAssemblies
                        .FirstOrDefault(assembly => String.Compare(Path.GetFileName(assembly.Location), file.Name, StringComparison.OrdinalIgnoreCase) == 0) == null);

                var modulesDict = moduleFiles.ToDictionary(k => k.Name.ToLower());

                MemoryStream ms = new MemoryStream();
                string connectionString = P2.Cuberry.Framework.Helper.Configuration.ConfigurationSetUp.GetEntConnectionString("Business", Properties.Settings.Default.EntityResourcesSecurity);
                using (DAL.SecurityDataClassesDataContext db = new DAL.SecurityDataClassesDataContext(connectionString))
                {
                    var modules = from m in db.XConfigModules
                                  select new { m.ModuleName, m.Version, m.FileName };
                    foreach (var module in modules)
                    {
                        if (modulesDict.ContainsKey(module.FileName.ToLower()))
                        {
                            // module exists on disc
                            var moduleFile = modulesDict[module.FileName.ToLower()];
                            FileVersionInfo vi = FileVersionInfo.GetVersionInfo(moduleFile.FullName);
                            if (IsHigherVersion(module.Version, vi.FileVersion))
                            {
                                LoadModuleFromDb(module.ModuleName);
                            }
                        }
                        else
                        {
                            LoadModuleFromDb(module.ModuleName);
                        }
                    }
                }
            }
            catch (Exception ex) //AM:TC
            {
                //P2Log.LogError(Security.User.Current.UserID, string.Format("LoadModulesFromDb({0})", location), ex);
                P2ExceptionHandler.LogException(ex, string.Format("{0}.{1}", "P2ModuleRepository", System.Reflection.MethodInfo.GetCurrentMethod().Name));
            }
        }


        private static void LoadModulesFromFileSystem(string location)
        {
            // check filesystem
            DirectoryInfo directory = new DirectoryInfo(location);
            Assembly[] alreadyLoadedAssemblies = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies();
            var moduleFiles = directory.GetFiles("*.dll")
                .Where(file => alreadyLoadedAssemblies
                    .FirstOrDefault(assembly => String.Compare(Path.GetFileName(assembly.Location), file.Name, StringComparison.OrdinalIgnoreCase) == 0) == null);

            var modulesDict = moduleFiles.ToDictionary(k => k.Name.ToLower());

            DirectoryInfo sourceDirectory = new DirectoryInfo(ModulesRepositoryPath);
            var sourceModuleFiles = sourceDirectory.GetFiles("*.dll");

            try
            {
                foreach (var sourceModule in sourceDirectory.GetFiles())
                {
                    if (modulesDict.ContainsKey(sourceModule.Name.ToLower()))
                    {
                        // module exists on disc
                        var moduleFile = modulesDict[sourceModule.Name.ToLower()];
                        FileVersionInfo vi = FileVersionInfo.GetVersionInfo(moduleFile.FullName);
                        FileVersionInfo viSource = FileVersionInfo.GetVersionInfo(sourceModule.FullName);
                        
                        if (IsHigherVersion(viSource.FileVersion, vi.FileVersion))
                        {
                            LoadModuleFromFileSystem(sourceModule.Name);
                        }
                    }
                    else
                    {
                        LoadModuleFromFileSystem(sourceModule.Name);
                    }
                }
            }
            catch (System.Exception ex) //AM:TC
            {
                P2ExceptionHandler.LogException(ex, string.Format("{0}.{1}", "P2ModuleRepository", System.Reflection.MethodInfo.GetCurrentMethod().Name));
            //  P2Log.LogError(Security.User.Current.UserID, string.Format("LoadModulesFromFileSystem({0})", location), ex);

            }
        }


        private static bool IsHigherVersion(string newVersion, string originalVersion)
        {
            try
            {
                if (string.IsNullOrEmpty(newVersion))
                    return false;
                if (string.IsNullOrEmpty(originalVersion))
                    return true;
                string[] arrNewVersion = newVersion.Split('.');
                string[] arrOriginalVersion = originalVersion.Split('.');
                if (arrNewVersion.Length >= 4 && arrOriginalVersion.Length >= 4)
                {
                    if (int.Parse(arrNewVersion[0]) > int.Parse(arrOriginalVersion[0]))
                        return true;

                    if (int.Parse(arrNewVersion[0]) == int.Parse(arrOriginalVersion[0]))
                    {
                        if (int.Parse(arrNewVersion[1]) > int.Parse(arrOriginalVersion[1]))
                            return true;
                        if (int.Parse(arrNewVersion[1]) == int.Parse(arrOriginalVersion[1]))
                        {
                            if (int.Parse(arrNewVersion[2]) > int.Parse(arrOriginalVersion[2]))
                                return true;
                            if (int.Parse(arrNewVersion[2]) == int.Parse(arrOriginalVersion[2]))
                            {
                                if (int.Parse(arrNewVersion[3]) >= int.Parse(arrOriginalVersion[3]))
                                    return true;
                            }
                        }
                    }
                }
                return false;
            }
            catch (System.Exception ex) //AM:TC
            {
                P2ExceptionHandler.LogException(ex, string.Format("{0}.{1}", "P2ModuleRepository", System.Reflection.MethodInfo.GetCurrentMethod().Name));
                return false;
            }
        }


        public static void SaveModule(string moduleName, string version, string location)
        {
            try
            {
                if (string.IsNullOrEmpty(ModulesRepositoryType) || ModulesRepositoryType == "none")
                {
                }
                else if (ModulesRepositoryType == "database")
                {
                    SaveModuleToDb(moduleName, version, location);
                }
            }
            catch (System.Exception ex) //AM:TC
            {
                P2ExceptionHandler.LogException(ex, string.Format("{0}.{1}", "P2ModuleRepository", System.Reflection.MethodInfo.GetCurrentMethod().Name));
            }
        }


        public static void LoadModules(string location)
        {
            try
            {
                if (string.IsNullOrEmpty(ModulesRepositoryType) || ModulesRepositoryType == "none")
                {
                }
                else if (ModulesRepositoryType == "database")
                {
                    LoadModulesFromDb(location);
                }
                else
                {
                    LoadModulesFromFileSystem(location);
                }
            }
            catch (System.Exception ex) //AM:TC
            {
                P2ExceptionHandler.LogException(ex, string.Format("{0}.{1}", "P2ModuleRepository", System.Reflection.MethodInfo.GetCurrentMethod().Name));
            }
        }


        public static bool IsAddModuleAllowed()
        {
            if (ModulesRepositoryType == "database")
            {
                return true;
            }
            return false;
        }


    }
}
